/*
 * This file is part of Ember.
 *
 * Ember is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ember is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ember.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) Grant Tyra 2010
 * granttyra@gmail.com
*/

#include "ember_statement.h"
#include "ember_string.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

char *statement_build(EmberStatement**, EmberList*, int, int, int*);

void *statement_raw(EmberStatement *statement) {
	return statement->subs;
}

EmberStatement *statement_create(int type) {
	EmberStatement *statement = malloc(sizeof(EmberStatement));
	statement->type = type;
	statement->subs = type == STATEMENT_RAW ? NULL : EmberListCreate();
	statement->code = NULL;
	return statement;
}

char *statement_create_raw(EmberStatement **statement, const char *value) {
	*statement = statement_create(STATEMENT_RAW);
	(*statement)->subs = EmberStringCopy(value);
	(*statement)->code = EmberStringCopy(value);
	return NULL;
}

int statement_sub_count(EmberStatement *statement) {
	if (statement->type == STATEMENT_RAW) {
		return -1;
	}

	return ((EmberList*)statement->subs)->size;
}

void statement_destroy(EmberStatement *statement) {
	int i = 0;
	int sub_count = 0;
	EmberStatement *sub = NULL;

	if (statement->type == STATEMENT_RAW) {
		EmberStringDestroy(statement->subs);
		free(statement);
		return;
	}

	sub_count = statement_sub_count(statement);

	for (i = 0; i < sub_count; i++) {
		sub = statement_sub(statement, i);
		statement_destroy(sub);
	}

	EmberListDestroy(statement->subs);
	EmberStringDestroy(statement->code);
	free(statement);
}

void statement_add_sub(EmberStatement *statement, EmberStatement *sub) {
	EmberListAdd(statement->subs, sub);
}

char *statement_build_assignment(EmberStatement **statement, const char *line) {
	EmberStatement *lefthand = NULL;
	EmberStatement *righthand = NULL;
	char *lefthandstr = NULL;
	char *righthandstr = NULL;

	*statement = statement_create(STATEMENT_ASSIGN);
	(*statement)->code = EmberStringCopy(line);

	lefthandstr = EmberStringBefore(line, '=');
	righthandstr = EmberStringAfter(line, '=');

	EmberStringTrim(&lefthandstr);
	EmberStringTrim(&righthandstr);

	statement_create_raw(&lefthand, lefthandstr);
	statement_build_single(&righthand, righthandstr);

	statement_add_sub(*statement, lefthand);
	statement_add_sub(*statement, righthand);

	return NULL;
}

char *statement_build_value(EmberStatement **statement, const char *line) {
	EmberStatement *symbol = NULL;
	char *symbolstr = NULL;

	symbolstr = EmberStringCopy(line);
	EmberStringTrim(&symbolstr);

	*statement = statement_create(STATEMENT_VALUE);
	(*statement)->code = EmberStringCopy(line);

	statement_create_raw(&symbol, symbolstr);
   
	statement_add_sub(*statement, symbol);

	EmberStringDestroy(symbolstr);

	return NULL;
}

char *statement_build_call(EmberStatement **statement, const char *line) {
	int i = 0;
	EmberStatement *sub = NULL;
	EmberStatement *receiver = NULL;
	EmberStatement *method = NULL;
	char *arglist = NULL;
	char *arg = NULL;
	char *callsig = NULL;
	char *receiverstr = NULL;
	char *methodstr = NULL;
	EmberList *args = NULL;

	*statement = statement_create(STATEMENT_CALL);
	(*statement)->code = EmberStringCopy(line);

	arglist = EmberStringReverseBetween(line, '(', ')');
	callsig = EmberStringReverseBefore(line, '(');
	receiverstr = EmberStringReverseBefore(callsig, '.');
	methodstr = EmberStringReverseAfter(callsig, '.');

	EmberStringTrim(&arglist);
	EmberStringTrim(&callsig);
	EmberStringTrim(&receiverstr);
	EmberStringTrim(&methodstr);

	statement_build_single(&receiver, receiverstr);
	statement_create_raw(&method, methodstr);

	statement_add_sub(*statement, receiver);
	statement_add_sub(*statement, method);

	if (strlen(arglist) > 0) {
		args = EmberListCreate();
		EmberStringSplit(arglist, ',', args);

		for (i = 0; i < args->size; i++) {
			arg = EmberListGet(args, i);
			EmberStringTrim(&arg);
			statement_build_single(&sub, arg);
			statement_add_sub(*statement, sub);
			EmberStringDestroy(arg);
		}
		
		EmberListDestroy(args);
	}

	EmberStringDestroy(receiverstr);
	EmberStringDestroy(methodstr);
	EmberStringDestroy(callsig);
	EmberStringDestroy(arglist);

	return NULL;
}

char *statement_build_static(EmberStatement **statement, const char *line) {
	int i = 0;
	EmberStatement *sub = NULL;
	EmberStatement *receiver = NULL;
	EmberStatement *method = NULL;
	char *arglist = NULL;
	char *arg = NULL;
	char *callsig = NULL;
	char *receiverstr = NULL;
	char *methodstr = NULL;
	EmberList *args = NULL;

	*statement = statement_create(STATEMENT_STATIC);
	(*statement)->code = EmberStringCopy(line);

	arglist = EmberStringReverseBetween(line, '(', ')');
	callsig = EmberStringBefore(line, '(');
	receiverstr = EmberStringReverseBefore(callsig, ':');
	methodstr = EmberStringReverseAfter(callsig, ':');

	EmberStringTrim(&arglist);
	EmberStringTrim(&callsig);
	EmberStringTrim(&receiverstr);
	EmberStringTrim(&methodstr);

	statement_create_raw(&receiver, receiverstr);
	statement_create_raw(&method, methodstr);

	statement_add_sub(*statement, receiver);
	statement_add_sub(*statement, method);

	if (strlen(arglist) > 0) {
		args = EmberListCreate();
		EmberStringSplit(arglist, ',', args);

		for (i = 0; i < args->size; i++) {
			arg = EmberListGet(args, i);
			EmberStringTrim(&arg);
			statement_build_single(&sub, arg);
			statement_add_sub(*statement, sub);
			EmberStringDestroy(arg);
		}
		
		EmberListDestroy(args);
	}

	EmberStringDestroy(receiverstr);
	EmberStringDestroy(methodstr);
	EmberStringDestroy(callsig);
	EmberStringDestroy(arglist);

	return NULL;
}

void *statement_sub(EmberStatement *root, int index) {
	return EmberListGet(root->subs, index);
}

char *statement_build_if(EmberStatement **statement, EmberList *lines, int begin, int end, int *past) {
	char *expressionstr = NULL;
	char *line = NULL;
	EmberStatement *expression = NULL;
	EmberStatement *block = NULL;
	EmberStatement *subif = NULL;

	*statement = statement_create(STATEMENT_IF);

	line = EmberListGet(lines, begin);
	EmberStringTrim(&line);

	(*statement)->code = EmberStringCopy(line);

	if (EmberStringStartsWithString(line, "elseif ")) {
		expressionstr = EmberStringAfterString(line, "elseif ");
	} else if (EmberStringStartsWithString(line, "if ")) {
		expressionstr = EmberStringAfterString(line, "if ");
	} else if (EmberStringStartsWithString(line, "else")) {
		expressionstr = EmberStringCopy("1");
	}

	EmberStringTrim(&expressionstr);

	statement_build_single(&expression, expressionstr);
	statement_build_root(&block, lines, begin + 1, end, past);

	statement_add_sub(*statement, expression);
	statement_add_sub(*statement, block);

	line = EmberListGet(lines, *past);

	if (EmberStringStartsWithString(line, "elseif") || EmberStringStartsWithString(line, "else")) {
		statement_build_if(&subif, lines, *past, end, past);
		statement_add_sub(*statement, subif);
	}

	EmberStringDestroy(expressionstr);
	
	return NULL;
}

char *statement_build_while(EmberStatement **statement, EmberList *lines, int begin, int end, int *past) {
	char *expressionstr = NULL;
	char *line = NULL;
	EmberStatement *expression = NULL;
	EmberStatement *block = NULL;

	*statement = statement_create(STATEMENT_WHILE);

	line = EmberListGet(lines, begin);
	expressionstr = EmberStringAfterString(line, "while ");
	EmberStringTrim(&expressionstr);

	(*statement)->code = EmberStringCopy(line);

	statement_build_single(&expression, expressionstr);
	statement_build_root(&block, lines, begin + 1, end, past);

	statement_add_sub(*statement, expression);
	statement_add_sub(*statement, block);

	EmberStringDestroy(expressionstr);
	
	return NULL;
}

char *statement_build_for(EmberStatement **statement, EmberList *lines, int begin, int end, int *past) {
	char *expressionstr = NULL;
	char *prestr = NULL;
	char *checkstr = NULL;
	char *poststr = NULL;
	char *line = NULL;
	EmberStatement *pre = NULL;
	EmberStatement *check = NULL;
	EmberStatement *post = NULL;
	EmberStatement *block = NULL;
	EmberList *parts = NULL;

	*statement = statement_create(STATEMENT_FOR);

	line = EmberListGet(lines, begin);
	expressionstr = EmberStringAfterString(line, "for ");
	EmberStringTrim(&expressionstr);

	(*statement)->code = EmberStringCopy(line);

	parts = EmberListCreate();
	EmberStringSplit(expressionstr, ',', parts);

	prestr = EmberListGet(parts, 0);
	checkstr = EmberListGet(parts, 1);
	poststr = EmberListGet(parts, 2);

	EmberStringTrim(&prestr);
	EmberStringTrim(&checkstr);
	EmberStringTrim(&poststr);

	statement_build_single(&pre, prestr);
	statement_build_single(&check, checkstr);
	statement_build_single(&post, poststr);
	statement_build_root(&block, lines, begin + 1, end, past);

	statement_add_sub(*statement, pre);
	statement_add_sub(*statement, check);
	statement_add_sub(*statement, post);
	statement_add_sub(*statement, block);

	EmberStringDestroy(expressionstr);
	EmberStringDestroy(prestr);
	EmberStringDestroy(poststr);
	EmberStringDestroy(checkstr);
	
	EmberListDestroy(parts);
	
	return NULL;
}

char *statement_build_new(EmberStatement **statement, const char *line) {
	EmberStatement *class_name = NULL;
	char *class_namestr = NULL;
	
	*statement = statement_create(STATEMENT_NEW);
	(*statement)->code = EmberStringCopy(line);

	class_namestr = EmberStringAfterString(line, "new ");
	EmberStringTrim(&class_namestr);

	statement_create_raw(&class_name, class_namestr);
	statement_add_sub(*statement, class_name);

	EmberStringDestroy(class_namestr);
	return NULL;
}

char *statement_build_return(EmberStatement **statement, const char *line) {
	EmberStatement *expression = NULL;
	char *expressionstr = NULL;
	
	*statement = statement_create(STATEMENT_RETURN);
	(*statement)->code = EmberStringCopy(line);

	expressionstr = EmberStringAfterString(line, "return ");
	EmberStringTrim(&expressionstr);
	
	statement_build_single(&expression, expressionstr);

	statement_add_sub(*statement, expression);

	EmberStringDestroy(expressionstr);
	return NULL;
}

char *statement_build_2c_op(EmberStatement **statement, int index, const char *line, const char *methodstr) {
	EmberStatement *lefthand = NULL;
	EmberStatement *righthand = NULL;
	EmberStatement *method = NULL;
	char *lefthandstr = NULL;
	char *righthandstr = NULL;

	lefthandstr = EmberStringSubstring(line, 0, index);
	righthandstr = EmberStringSubstring(line, index + 2, strlen(line));

	EmberStringTrim(&lefthandstr);
	EmberStringTrim(&righthandstr);

	*statement = statement_create(STATEMENT_CALL);
	(*statement)->code = EmberStringCopy(line);

	statement_build_single(&lefthand, lefthandstr);
	statement_build_single(&righthand, righthandstr);
	statement_create_raw(&method, methodstr);

	statement_add_sub(*statement, lefthand);
	statement_add_sub(*statement, method);
	statement_add_sub(*statement, righthand);

	EmberStringDestroy(lefthandstr);
	EmberStringDestroy(righthandstr);

	return NULL;
}

char *statement_build_op(EmberStatement **statement, int index, const char *line, const char *methodstr) {
	EmberStatement *lefthand = NULL;
	EmberStatement *righthand = NULL;
	EmberStatement *method = NULL;
	char *lefthandstr = NULL;
	char *righthandstr = NULL;

	lefthandstr = EmberStringSubstring(line, 0, index);
	righthandstr = EmberStringSubstring(line, index + 1, strlen(line));

	EmberStringTrim(&lefthandstr);
	EmberStringTrim(&righthandstr);

	*statement = statement_create(STATEMENT_CALL);
	(*statement)->code = EmberStringCopy(line);

	statement_build_single(&lefthand, lefthandstr);
	statement_build_single(&righthand, righthandstr);
	statement_create_raw(&method, methodstr);

	statement_add_sub(*statement, lefthand);
	statement_add_sub(*statement, method);
	statement_add_sub(*statement, righthand);

	EmberStringDestroy(lefthandstr);
	EmberStringDestroy(righthandstr);

	return NULL;
}

char *statement_build_inc(EmberStatement **statement, const char *line) {
	char *receiverstr = EmberStringBeforeString(line, "++");
	EmberStatement *receiver = NULL;
	EmberStatement *method = NULL;
	statement_build_single(&receiver, receiverstr);
	statement_create_raw(&method, "increment");
	*statement = statement_create(STATEMENT_CALL);
	statement_add_sub(*statement, receiver);
	statement_add_sub(*statement, method);
	EmberStringDestroy(receiverstr);
	(*statement)->code = EmberStringCopy(line);
	return NULL;
}

char *statement_build_dec(EmberStatement **statement, const char *line) {
	char *receiverstr = EmberStringBeforeString(line, "--");
	EmberStatement *receiver = NULL;
	EmberStatement *method = NULL;
	statement_build_single(&receiver, receiverstr);
	statement_create_raw(&method, "decrement");
	*statement = statement_create(STATEMENT_CALL);
	statement_add_sub(*statement, receiver);
	statement_add_sub(*statement, method);
	EmberStringDestroy(receiverstr);
	(*statement)->code = EmberStringCopy(line);
	return NULL;
}

char *statement_build_operation(EmberStatement **statement, const char *line) {
	int index = -1;

	if (EmberStringReverseContainsString(line, "==")) {
		index = EmberStringReverseFindString(line, "==");
		return statement_build_2c_op(statement, index, line, "equalTo");
	}
	
	if (EmberStringReverseContainsString(line, "||")) {
		index = EmberStringReverseFindString(line, "||");
		return statement_build_2c_op(statement, index, line, "booleanOr");
	}
	
	if (EmberStringReverseContainsString(line, "&&")) {
		index = EmberStringReverseFindString(line, "&&");
		return statement_build_2c_op(statement, index, line, "booleanAnd");
	}
	
	if (EmberStringReverseContainsString(line, "!=")) {
		index = EmberStringReverseFindString(line, "!=");
		return statement_build_2c_op(statement, index, line, "notEqualTo");
	}
	
	if (EmberStringReverseContainsString(line, "<=")) {
		index = EmberStringReverseFindString(line, "<=");
		return statement_build_2c_op(statement, index, line, "lessThanOrEqualTo");
	}
	
	if (EmberStringReverseContainsString(line, ">=")) {
		index = EmberStringReverseFindString(line, ">=");
		return statement_build_2c_op(statement, index, line, "greaterThanOrEqualTo");
	}
	
	if (EmberStringReverseContains(line, '+')) {
		index = EmberStringReverseFind(line, '+');
		return statement_build_op(statement, index, line, "add");
	}
	
	if (EmberStringReverseContains(line, '-')) {
		index = EmberStringReverseFind(line, '-');
		return statement_build_op(statement, index, line, "subtract");
	}
	
	if (EmberStringReverseContains(line, '*')) {
		index = EmberStringReverseFind(line, '*');
		return statement_build_op(statement, index, line, "multiply");
	}
	
	if (EmberStringReverseContains(line, '/')) {
		index = EmberStringReverseFind(line, '/');
		return statement_build_op(statement, index, line, "divide");
	}
	
	if (EmberStringReverseContains(line, '<')) {
		index = EmberStringReverseFind(line, '<');
		return statement_build_op(statement, index, line, "lessThan");
	}
	
	if (EmberStringReverseContains(line, '>')) {
		index = EmberStringReverseFind(line, '>');
		return statement_build_op(statement, index, line, "greaterThan");
	} 
	
	return NULL;
}

char *statement_build_dict(EmberStatement **statement, const char *line) {
	char *elementstr = NULL;
	char *pairstr = NULL;
	char *keystr = NULL;
	char *valuestr = NULL;
	EmberList *elements = NULL;
	EmberStatement *key = NULL;
	EmberStatement *value = NULL;
	int i = 0;

	elementstr = EmberStringBetween(line, '{', '}');

	elements = EmberListCreate();
	EmberStringSplit(elementstr, ',', elements);

	*statement = statement_create(STATEMENT_DICT);
	(*statement)->code = EmberStringCopy(line);

	for (i = 0; i < elements->size; i++) {
		pairstr = EmberListGet(elements, i);
		EmberStringTrim(&pairstr);

		keystr = EmberStringBeforeString(pairstr, "=>");
		valuestr = EmberStringAfterString(pairstr, "=>");

		statement_build_single(&key, keystr);
		statement_build_single(&value, valuestr);

		statement_add_sub(*statement, key);
		statement_add_sub(*statement, value);

		EmberStringDestroy(pairstr);
		EmberStringDestroy(keystr);
		EmberStringDestroy(valuestr);
	}
	
	EmberListDestroy(elements);
	EmberStringDestroy(elementstr);

	return NULL;
}

char *statement_build_list(EmberStatement **statement, const char *line) {
	char *elementliststr = NULL;
	char *elementstr = NULL;
	EmberList *elements = NULL;
	EmberStatement *element = NULL;
	int i = 0;

	elementliststr = EmberStringBetween(line, '[', ']');

	elements = EmberListCreate();
	EmberStringSplit(elementliststr, ',', elements);

	*statement = statement_create(STATEMENT_LIST);
	(*statement)->code = EmberStringCopy(line);

	for (i = 0; i < elements->size; i++) {
		elementstr = EmberListGet(elements, i);
		EmberStringTrim(&elementstr);
		
		statement_build_single(&element, elementstr);
		statement_add_sub(*statement, element);

		EmberStringDestroy(elementstr);
	}
	
	EmberListDestroy(elements);
	EmberStringDestroy(elementliststr);

	return NULL;
}

char *statement_build_single(EmberStatement **statement, const char *line) {
	char c = 0;
	unsigned int i = 0;
	int ignore = 0;

	if (strcmp(line, "break") == 0) {
		*statement = statement_create(STATEMENT_BREAK);
		return NULL;
	}

	if (strcmp(line, "continue") == 0) {
		*statement = statement_create(STATEMENT_CONTINUE);
		return NULL;
	}
 
	if (EmberStringContains(line, '=') && EmberStringFind(line, '=') != EmberStringFindString(line, "==") && EmberStringFind(line, '=') != EmberStringFindString(line, "=>") && EmberStringFind(line, '=') != EmberStringFindString(line, "!=") + 1) {
		return statement_build_assignment(statement, line);
	} 
	
	if (EmberStringStartsWithString(line, "new ")) {
		return statement_build_new(statement, line);
	}
	
	if (EmberStringStartsWithString(line, "return ")) {
		return statement_build_return(statement, line);
	} 

	if (EmberStringStartsWith(line, '[')) {
		return statement_build_list(statement, line);
	}
	
	if (EmberStringStartsWith(line, '{')) {
		return statement_build_dict(statement, line);
	}

	if (EmberStringContainsString(line, "==")) {
		return statement_build_operation(statement, line);
	}

	if (EmberStringContainsString(line, "!=")) {
		return statement_build_operation(statement, line);
	}
	
	if (EmberStringContainsString(line, "||")) {
		return statement_build_operation(statement, line);
	}
	
	if (EmberStringContainsString(line, "&&")) {
		return statement_build_operation(statement, line);
	}

	if (EmberStringEndsWithString(line, "++")) {
		return statement_build_inc(statement, line);
	}
	
	if (EmberStringEndsWithString(line, "--")) {
		return statement_build_dec(statement, line);
	}

	ignore = 0;

	for (i = 0; i < strlen(line); i++) {
		c = line[i];

		if (c == '"') {
			ignore = !ignore;
		} else if (c == '(') {
			ignore++;
		} else if (c == ')') {
			ignore--;
		}

		if (ignore) {
			continue;
		}

		switch (c) {
			case '*':
			case '+':
			case '/':
			case '-':
			case '<':
			case '>':
				return statement_build_operation(statement, line);
			case ':':
				return statement_build_static(statement, line);
			case '.':
				if (!EmberStringContains(line, '(') || !EmberStringContains(line, ')')) {
					continue;
				}

				return statement_build_call(statement, line);
			default:
				continue;
		}
	}

	return statement_build_value(statement, line);
}

char *statement_build(EmberStatement **statement, EmberList *lines, int start, int end, int *past) {
	const char *line = EmberListGet(lines, start);

	if (EmberStringStartsWithString(line, "if ")) {
		return statement_build_if(statement, lines, start, end, past);
	}
	
	if (EmberStringStartsWithString(line, "while ")) {
		return statement_build_while(statement, lines, start, end, past);
	}
	
	if (EmberStringStartsWithString(line, "for ")) {
		return statement_build_for(statement, lines, start, end, past);
	} 

	return statement_build_single(statement, line);
}

char *statement_build_root(EmberStatement **statement, EmberList *lines, int start, int end, int *past) {
	EmberStatement *sub = NULL;
	char *line = NULL;
	int i = 0;
	char *error = NULL;

	*statement = statement_create(STATEMENT_ROOT);

	for (i = start; i < end; i++) {
		line = EmberListGet(lines, i);

		if (line == NULL || EmberStringIsEmpty(line)) {
			continue;	
		} else if (EmberStringContainsString(line, "end")) {
			break;
		} else if (EmberStringContainsString(line, "elseif")) {
			break;
		} else if (EmberStringContainsString(line, "else")) {
			break;
		}

		error = statement_build(&sub, lines, i, end, &i);

		if (error != NULL) {
			return error;
		}

		statement_add_sub(*statement, sub);
	}

	*past = i;

	return NULL;
}
